using System.IO;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace OmniSharp.Extensions.JsonRpc.Generators
{
    [Generator]
    public class EnumLikeStringGenerator : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
            var syntaxProvider = context.SyntaxProvider.CreateSyntaxProvider(
                (node, _) => node is StructDeclarationSyntax tds && tds.AttributeLists.ContainsAttribute("StringEnum"),
                (syntaxContext, _) => syntaxContext
            );

            context.RegisterSourceOutput(syntaxProvider, GenerateEnum);
        }

        private void GenerateEnum(SourceProductionContext context, GeneratorSyntaxContext syntaxContext)
        {
            var candidate = (StructDeclarationSyntax)syntaxContext.Node;
            var model = syntaxContext.SemanticModel;
            var symbol = model.GetDeclaredSymbol(syntaxContext.Node);
            if (symbol is null) return;

            if (!candidate.Modifiers.Any(z => z.IsKind(SyntaxKind.PartialKeyword)))
            {
                context.ReportDiagnostic(Diagnostic.Create(GeneratorDiagnostics.MustBePartial, candidate.Identifier.GetLocation(), candidate.Identifier.Text));
                return;
            }

            if (!candidate.Modifiers.Any(z => z.IsKind(SyntaxKind.ReadOnlyKeyword)))
            {
                context.ReportDiagnostic(Diagnostic.Create(GeneratorDiagnostics.MustBeReadOnly, candidate.Identifier.GetLocation(), candidate.Identifier.Text));
                return;
            }

            var cu = CompilationUnit(
                         List<ExternAliasDirectiveSyntax>(), List<UsingDirectiveSyntax>(), List<AttributeListSyntax>(), SingletonList<MemberDeclarationSyntax>(
                             NamespaceDeclaration(ParseName(symbol.ContainingNamespace.ToDisplayString()))
                                .WithMembers(SingletonList<MemberDeclarationSyntax>(GetImplementation(candidate)))
                         )
                     )
                    .AddUsings(
                         UsingDirective(ParseName("System")), UsingDirective(ParseName("System.Collections.Generic")),
                         UsingDirective(ParseName("System.Diagnostics")), UsingDirective(ParseName("System.Linq")),
                         UsingDirective(ParseName("System.Reflection")), UsingDirective(ParseName("Newtonsoft.Json")),
                         UsingDirective(ParseName("OmniSharp.Extensions.JsonRpc")),
                         UsingDirective(ParseName("OmniSharp.Extensions.JsonRpc.Serialization.Converters"))
                     )
                    .WithLeadingTrivia()
                    .WithTrailingTrivia()
                    .WithLeadingTrivia(Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true)))
                    .WithTrailingTrivia(Trivia(NullableDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)));

            context.AddSource(
                $"{Path.GetFileNameWithoutExtension(candidate.SyntaxTree.FilePath)}_{candidate.Identifier.Text}{( candidate.Arity > 0 ? candidate.Arity.ToString() : "" )}.cs",
                cu.NormalizeWhitespace().GetText(Encoding.UTF8)
            );
        }

        private static StructDeclarationSyntax GetImplementation(StructDeclarationSyntax syntax)
        {
            var items = syntax.Members
                              .OfType<PropertyDeclarationSyntax>()
                              .Where(z => z.Modifiers.Any(SyntaxKind.StaticKeyword))
                              .Select(z => IdentifierName(z.Identifier.Text))
                              .Concat(
                                   syntax.Members
                                         .OfType<FieldDeclarationSyntax>()
                                         .Where(z => z.Modifiers.Any(SyntaxKind.StaticKeyword))
                                         .Where(z => z.Declaration.Variables.Count == 1)
                                         .Select(z => IdentifierName(z.Declaration.Variables[0].Identifier.Text))
                               );
            return syntax
                  .WithBaseList(
                       BaseList(
                           SeparatedList(
                               new BaseTypeSyntax[]
                               {
                                   SimpleBaseType(
                                       GenericName(Identifier("IEquatable"))
                                          .WithTypeArgumentList(
                                               TypeArgumentList(SingletonSeparatedList<TypeSyntax>(PredefinedType(Token(SyntaxKind.StringKeyword))))
                                           )
                                   ),
                                   SimpleBaseType(
                                       GenericName(Identifier("IEquatable"))
                                          .WithTypeArgumentList(TypeArgumentList(SingletonSeparatedList<TypeSyntax>(IdentifierName(syntax.Identifier.Text))))
                                   ),
                                   SimpleBaseType(IdentifierName("IEnumLikeString"))
                               }
                           )
                       )
                   )
                  .WithAttributeLists(
                       List(
                           new[]
                           {
                               AttributeList(
                                   SingletonSeparatedList(
                                       Attribute(IdentifierName("JsonConverter"))
                                          .WithArgumentList(
                                               AttributeArgumentList(
                                                   SingletonSeparatedList(
                                                       AttributeArgument(
                                                           TypeOfExpression(
                                                               IdentifierName("EnumLikeStringConverter")
                                                           )
                                                       )
                                                   )
                                               )
                                           )
                                   )
                               ),
                               AttributeList(
                                   SingletonSeparatedList(
                                       Attribute(IdentifierName("DebuggerDisplay"))
                                          .WithArgumentList(
                                               AttributeArgumentList(
                                                   SingletonSeparatedList(
                                                       AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("{_value}")))
                                                   )
                                               )
                                           )
                                   )
                               )
                           }
                       )
                   )
                  .WithMembers(
                       List(
                           new MemberDeclarationSyntax[]
                           {
                               FieldDeclaration(
                                       VariableDeclaration(
                                               GenericName(Identifier("Lazy"))
                                                  .WithTypeArgumentList(
                                                       TypeArgumentList(
                                                           SingletonSeparatedList<TypeSyntax>(
                                                               GenericName(Identifier("IReadOnlyList"))
                                                                  .WithTypeArgumentList(
                                                                       TypeArgumentList(
                                                                           SingletonSeparatedList<TypeSyntax>(IdentifierName(syntax.Identifier.Text))
                                                                       )
                                                                   )
                                                           )
                                                       )
                                                   )
                                           )
                                          .WithVariables(
                                               SingletonSeparatedList(
                                                   VariableDeclarator(Identifier("_defaults"))
                                                      .WithInitializer(
                                                           EqualsValueClause(
                                                               ObjectCreationExpression(
                                                                       GenericName(Identifier("Lazy"))
                                                                          .WithTypeArgumentList(
                                                                               TypeArgumentList(
                                                                                   SingletonSeparatedList<TypeSyntax>(
                                                                                       GenericName(Identifier("IReadOnlyList"))
                                                                                          .WithTypeArgumentList(
                                                                                               TypeArgumentList(
                                                                                                   SingletonSeparatedList<TypeSyntax>(
                                                                                                       IdentifierName(syntax.Identifier.Text)
                                                                                                   )
                                                                                               )
                                                                                           )
                                                                                   )
                                                                               )
                                                                           )
                                                                   )
                                                                  .WithArgumentList(
                                                                       ArgumentList(
                                                                           SingletonSeparatedList(
                                                                               Argument(
                                                                                   ParenthesizedLambdaExpression()
                                                                                      .WithBlock(
                                                                                           Block(
                                                                                               SingletonList<StatementSyntax>(
                                                                                                   ReturnStatement(
                                                                                                       ArrayCreationExpression(
                                                                                                               ArrayType(IdentifierName(syntax.Identifier.Text))
                                                                                                                  .WithRankSpecifiers(
                                                                                                                       SingletonList(
                                                                                                                           ArrayRankSpecifier(
                                                                                                                               SingletonSeparatedList<
                                                                                                                                   ExpressionSyntax>(
                                                                                                                                   OmittedArraySizeExpression()
                                                                                                                               )
                                                                                                                           )
                                                                                                                       )
                                                                                                                   )
                                                                                                           )
                                                                                                          .WithInitializer(
                                                                                                               InitializerExpression(
                                                                                                                   SyntaxKind.ArrayInitializerExpression,
                                                                                                                   SeparatedList<ExpressionSyntax>(items)
                                                                                                               )
                                                                                                           )
                                                                                                   )
                                                                                               )
                                                                                           )
                                                                                       )
                                                                               )
                                                                           )
                                                                       )
                                                                   )
                                                           )
                                                       )
                                               )
                                           )
                                   )
                                  .WithModifiers(
                                       TokenList(
                                           Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.ReadOnlyKeyword)
                                       )
                                   ),
                               PropertyDeclaration(
                                       GenericName(
                                               Identifier("IEnumerable")
                                           )
                                          .WithTypeArgumentList(
                                               TypeArgumentList(
                                                   SingletonSeparatedList<TypeSyntax>(
                                                       IdentifierName(syntax.Identifier.Text)
                                                   )
                                               )
                                           ),
                                       Identifier("Defaults")
                                   )
                                  .WithModifiers(
                                       TokenList(
                                           Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)
                                       )
                                   )
                                  .WithExpressionBody(
                                       ArrowExpressionClause(
                                           MemberAccessExpression(
                                               SyntaxKind.SimpleMemberAccessExpression,
                                               IdentifierName("_defaults"),
                                               IdentifierName("Value")
                                           )
                                       )
                                   )
                                  .WithSemicolonToken(
                                       Token(SyntaxKind.SemicolonToken)
                                   ),
                               FieldDeclaration(
                                       VariableDeclaration(
                                               PredefinedType(
                                                   Token(SyntaxKind.StringKeyword)
                                               )
                                           )
                                          .WithVariables(
                                               SingletonSeparatedList(
                                                   VariableDeclarator(
                                                       Identifier("_value")
                                                   )
                                               )
                                           )
                                   )
                                  .WithModifiers(
                                       TokenList(
                                           Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ReadOnlyKeyword)
                                       )
                                   ),
                               ConstructorDeclaration(
                                       Identifier(syntax.Identifier.Text)
                                   )
                                  .WithModifiers(
                                       TokenList(
                                           Token(SyntaxKind.PublicKeyword)
                                       )
                                   )
                                  .WithParameterList(
                                       ParameterList(
                                           SingletonSeparatedList(
                                               Parameter(
                                                       Identifier("type")
                                                   )
                                                  .WithType(
                                                       PredefinedType(
                                                           Token(SyntaxKind.StringKeyword)
                                                       )
                                                   )
                                           )
                                       )
                                   )
                                  .WithExpressionBody(
                                       ArrowExpressionClause(
                                           AssignmentExpression(
                                               SyntaxKind.SimpleAssignmentExpression,
                                               IdentifierName("_value"),
                                               IdentifierName("type")
                                           )
                                       )
                                   )
                                  .WithSemicolonToken(
                                       Token(SyntaxKind.SemicolonToken)
                                   ),
                               ConversionOperatorDeclaration(
                                       Token(SyntaxKind.ImplicitKeyword),
                                       IdentifierName(syntax.Identifier.Text)
                                   )
                                  .WithModifiers(
                                       TokenList(
                                           Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)
                                       )
                                   )
                                  .WithParameterList(
                                       ParameterList(
                                           SingletonSeparatedList(
                                               Parameter(
                                                       Identifier("kind")
                                                   )
                                                  .WithType(
                                                       PredefinedType(
                                                           Token(SyntaxKind.StringKeyword)
                                                       )
                                                   )
                                           )
                                       )
                                   )
                                  .WithExpressionBody(
                                       ArrowExpressionClause(
                                           ObjectCreationExpression(
                                                   IdentifierName(syntax.Identifier.Text)
                                               )
                                              .WithArgumentList(
                                                   ArgumentList(
                                                       SingletonSeparatedList(
                                                           Argument(
                                                               IdentifierName("kind")
                                                           )
                                                       )
                                                   )
                                               )
                                       )
                                   )
                                  .WithSemicolonToken(
                                       Token(SyntaxKind.SemicolonToken)
                                   ),
                               ConversionOperatorDeclaration(
                                       Token(SyntaxKind.ImplicitKeyword),
                                       PredefinedType(
                                           Token(SyntaxKind.StringKeyword)
                                       )
                                   )
                                  .WithModifiers(
                                       TokenList(
                                           Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)
                                       )
                                   )
                                  .WithParameterList(
                                       ParameterList(
                                           SingletonSeparatedList(
                                               Parameter(
                                                       Identifier("kind")
                                                   )
                                                  .WithType(
                                                       IdentifierName(syntax.Identifier.Text)
                                                   )
                                           )
                                       )
                                   )
                                  .WithExpressionBody(
                                       ArrowExpressionClause(
                                           MemberAccessExpression(
                                               SyntaxKind.SimpleMemberAccessExpression,
                                               IdentifierName("kind"),
                                               IdentifierName("_value")
                                           )
                                       )
                                   )
                                  .WithSemicolonToken(
                                       Token(SyntaxKind.SemicolonToken)
                                   ),
                               MethodDeclaration(
                                       PredefinedType(
                                           Token(SyntaxKind.StringKeyword)
                                       ),
                                       Identifier("ToString")
                                   )
                                  .WithModifiers(
                                       TokenList(
                                           Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword)
                                       )
                                   )
                                  .WithExpressionBody(
                                       ArrowExpressionClause(
                                           IdentifierName("_value")
                                       )
                                   )
                                  .WithSemicolonToken(
                                       Token(SyntaxKind.SemicolonToken)
                                   ),
                               MethodDeclaration(
                                       PredefinedType(
                                           Token(SyntaxKind.BoolKeyword)
                                       ),
                                       Identifier("Equals")
                                   )
                                  .WithModifiers(
                                       TokenList(
                                           Token(SyntaxKind.PublicKeyword)
                                       )
                                   )
                                  .WithParameterList(
                                       ParameterList(
                                           SingletonSeparatedList(
                                               Parameter(
                                                       Identifier("other")
                                                   )
                                                  .WithType(
                                                       IdentifierName(syntax.Identifier.Text)
                                                   )
                                           )
                                       )
                                   )
                                  .WithExpressionBody(
                                       ArrowExpressionClause(
                                           BinaryExpression(
                                               SyntaxKind.EqualsExpression,
                                               IdentifierName("_value"),
                                               MemberAccessExpression(
                                                   SyntaxKind.SimpleMemberAccessExpression,
                                                   IdentifierName("other"),
                                                   IdentifierName("_value")
                                               )
                                           )
                                       )
                                   )
                                  .WithSemicolonToken(
                                       Token(SyntaxKind.SemicolonToken)
                                   ),
                               MethodDeclaration(
                                       PredefinedType(
                                           Token(SyntaxKind.BoolKeyword)
                                       ),
                                       Identifier("Equals")
                                   )
                                  .WithModifiers(
                                       TokenList(
                                           Token(SyntaxKind.PublicKeyword)
                                       )
                                   )
                                  .WithParameterList(
                                       ParameterList(
                                           SingletonSeparatedList(
                                               Parameter(
                                                       Identifier("other")
                                                   )
                                                  .WithType(
                                                       PredefinedType(
                                                           Token(SyntaxKind.StringKeyword)
                                                       )
                                                   )
                                           )
                                       )
                                   )
                                  .WithExpressionBody(
                                       ArrowExpressionClause(
                                           BinaryExpression(
                                               SyntaxKind.EqualsExpression,
                                               IdentifierName("_value"),
                                               IdentifierName("other")
                                           )
                                       )
                                   )
                                  .WithSemicolonToken(
                                       Token(SyntaxKind.SemicolonToken)
                                   ),
                               MethodDeclaration(
                                       PredefinedType(
                                           Token(SyntaxKind.BoolKeyword)
                                       ),
                                       Identifier("Equals")
                                   )
                                  .WithModifiers(
                                       TokenList(
                                           Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword)
                                       )
                                   )
                                  .WithParameterList(
                                       ParameterList(
                                           SingletonSeparatedList(
                                               Parameter(
                                                       Identifier("obj")
                                                   )
                                                  .WithType(
                                                       PredefinedType(
                                                           Token(SyntaxKind.ObjectKeyword)
                                                       )
                                                   )
                                           )
                                       )
                                   ).WithExpressionBody(
                                       ArrowExpressionClause(
                                           BinaryExpression(
                                               SyntaxKind.LogicalOrExpression,
                                               BinaryExpression(
                                                   SyntaxKind.LogicalAndExpression,
                                                   IsPatternExpression(
                                                       IdentifierName("obj"),
                                                       DeclarationPattern(
                                                           PredefinedType(
                                                               Token(SyntaxKind.StringKeyword)
                                                           ),
                                                           SingleVariableDesignation(
                                                               Identifier("s")
                                                           )
                                                       )
                                                   ),
                                                   InvocationExpression(
                                                           IdentifierName("Equals")
                                                       )
                                                      .WithArgumentList(
                                                           ArgumentList(
                                                               SingletonSeparatedList(
                                                                   Argument(
                                                                       IdentifierName("s")
                                                                   )
                                                               )
                                                           )
                                                       )
                                               ),
                                               BinaryExpression(
                                                   SyntaxKind.LogicalAndExpression,
                                                   IsPatternExpression(
                                                       IdentifierName("obj"),
                                                       DeclarationPattern(
                                                           IdentifierName(syntax.Identifier.Text),
                                                           SingleVariableDesignation(
                                                               Identifier("other")
                                                           )
                                                       )
                                                   ),
                                                   InvocationExpression(
                                                           IdentifierName("Equals")
                                                       )
                                                      .WithArgumentList(
                                                           ArgumentList(
                                                               SingletonSeparatedList(
                                                                   Argument(
                                                                       IdentifierName("other")
                                                                   )
                                                               )
                                                           )
                                                       )
                                               )
                                           )
                                       )
                                   )
                                  .WithSemicolonToken(
                                       Token(SyntaxKind.SemicolonToken)
                                   ),
                               MethodDeclaration(
                                       PredefinedType(
                                           Token(SyntaxKind.IntKeyword)
                                       ),
                                       Identifier("GetHashCode")
                                   )
                                  .WithModifiers(
                                       TokenList(
                                           Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword)
                                       )
                                   )
                                  .WithExpressionBody(
                                       ArrowExpressionClause(
                                           InvocationExpression(
                                               MemberAccessExpression(
                                                   SyntaxKind.SimpleMemberAccessExpression,
                                                   IdentifierName("_value"),
                                                   IdentifierName("GetHashCode")
                                               )
                                           )
                                       )
                                   )
                                  .WithSemicolonToken(
                                       Token(SyntaxKind.SemicolonToken)
                                   ),
                               OperatorDeclaration(
                                       PredefinedType(
                                           Token(SyntaxKind.BoolKeyword)
                                       ),
                                       Token(SyntaxKind.EqualsEqualsToken)
                                   )
                                  .WithModifiers(
                                       TokenList(
                                           Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)
                                       )
                                   )
                                  .WithParameterList(
                                       ParameterList(
                                           SeparatedList<ParameterSyntax>(
                                               new SyntaxNodeOrToken[]
                                               {
                                                   Parameter(
                                                           Identifier("left")
                                                       )
                                                      .WithType(
                                                           IdentifierName(syntax.Identifier.Text)
                                                       ),
                                                   Token(SyntaxKind.CommaToken),
                                                   Parameter(
                                                           Identifier("right")
                                                       )
                                                      .WithType(
                                                           IdentifierName(syntax.Identifier.Text)
                                                       )
                                               }
                                           )
                                       )
                                   )
                                  .WithExpressionBody(
                                       ArrowExpressionClause(
                                           InvocationExpression(
                                                   MemberAccessExpression(
                                                       SyntaxKind.SimpleMemberAccessExpression,
                                                       IdentifierName("left"),
                                                       IdentifierName("Equals")
                                                   )
                                               )
                                              .WithArgumentList(
                                                   ArgumentList(
                                                       SingletonSeparatedList(
                                                           Argument(
                                                               IdentifierName("right")
                                                           )
                                                       )
                                                   )
                                               )
                                       )
                                   )
                                  .WithSemicolonToken(
                                       Token(SyntaxKind.SemicolonToken)
                                   ),
                               OperatorDeclaration(
                                       PredefinedType(
                                           Token(SyntaxKind.BoolKeyword)
                                       ),
                                       Token(SyntaxKind.ExclamationEqualsToken)
                                   )
                                  .WithModifiers(
                                       TokenList(
                                           Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)
                                       )
                                   )
                                  .WithParameterList(
                                       ParameterList(
                                           SeparatedList<ParameterSyntax>(
                                               new SyntaxNodeOrToken[]
                                               {
                                                   Parameter(
                                                           Identifier("left")
                                                       )
                                                      .WithType(
                                                           IdentifierName(syntax.Identifier.Text)
                                                       ),
                                                   Token(SyntaxKind.CommaToken),
                                                   Parameter(
                                                           Identifier("right")
                                                       )
                                                      .WithType(
                                                           IdentifierName(syntax.Identifier.Text)
                                                       )
                                               }
                                           )
                                       )
                                   )
                                  .WithExpressionBody(
                                       ArrowExpressionClause(
                                           PrefixUnaryExpression(
                                               SyntaxKind.LogicalNotExpression,
                                               InvocationExpression(
                                                       MemberAccessExpression(
                                                           SyntaxKind.SimpleMemberAccessExpression,
                                                           IdentifierName("left"),
                                                           IdentifierName("Equals")
                                                       )
                                                   )
                                                  .WithArgumentList(
                                                       ArgumentList(
                                                           SingletonSeparatedList(
                                                               Argument(
                                                                   IdentifierName("right")
                                                               )
                                                           )
                                                       )
                                                   )
                                           )
                                       )
                                   )
                                  .WithSemicolonToken(
                                       Token(SyntaxKind.SemicolonToken)
                                   )
                           }
                       )
                   )
                  .NormalizeWhitespace()
                ;
        }
    }
}
